﻿using SafeFile.Ciphers;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using static SafeFile.Ciphers.AES;

namespace SafeFile
{
    public partial class MainForm : Form
    {
        const string EXT = ".sfile";
        private Md5Form md5Form;

        public MainForm()
        {
            InitializeComponent();
            LoadKeys();
            LoadEncryptedFile();
        }

        private void LoadKeys()
        {
            var root = Path.GetDirectoryName(Application.ExecutablePath);

            var items = Directory.GetFiles(root, "*.key", SearchOption.TopDirectoryOnly);

            foreach (var item in items)
            {
                if (item.EndsWith("加密.key"))
                {
                    tbPubKey.Text = Path.Combine(root, item);
                }
                if (item.EndsWith("解密.key"))
                {
                    tbPrivKey.Text = Path.Combine(root, item);
                }
            }

            openKeyFolder.SelectedPath = root;
        }

        private void LoadEncryptedFile()
        {
            var root = Path.GetDirectoryName(Application.ExecutablePath);

            var items = Directory.GetFiles(root, "*" + EXT, SearchOption.TopDirectoryOnly);
            if (!items.Any())
            {
                return;
            }

            // 找出日期最大的
            var maxItem = items.Select(item =>
            {
                string[] temp = item.Split('.');
                if (temp.Length < 3)
                {
                    return null;
                }
                var date = temp[temp.Length - 2];
                if (!Regex.IsMatch(date, "\\d{8}"))
                {
                    return null;
                }
                return Tuple.Create(item, date);
            }).Where(item => item != null).Select(item => item.Item1).OrderBy(item => item).FirstOrDefault();

            if (maxItem == null)
            {
                maxItem = items.First();
            }
            tbFile.Text = Path.Combine(root, maxItem);
        }

        private void btMakeKeys_Click(object sender, EventArgs e)
        {
            if (openKeyFolder.ShowDialog() != DialogResult.OK)
            {
                return;
            }
            lbMsg.Text = "正在生成密钥...";
            Application.DoEvents();

            RSA.MakeKeys(openKeyFolder.SelectedPath);
            lbMsg.Text = "密钥生成完成";
            LoadKeys();
        }

        private void btSelectKey_Click(object sender, EventArgs e)
        {
            if (openTargetFile.ShowDialog() == DialogResult.Cancel)
            {
                return;
            }
            tbPubKey.Text = openTargetFile.FileName;
        }

        private void btSelectPrivKey_Click(object sender, EventArgs e)
        {
            if (openDecryptKeyFile.ShowDialog() == DialogResult.Cancel)
            {
                return;
            }
            tbPrivKey.Text = openDecryptKeyFile.FileName;
        }

        private void btSelectFile_Click(object sender, EventArgs e)
        {
            if (openEncryptKeyFile.ShowDialog() == DialogResult.Cancel)
            {
                return;
            }
            tbFile.Text = openEncryptKeyFile.FileName;
        }

        private bool checkFilePermission(string filename)
        {
            var file = new FileInfo(filename);
            if (!file.IsReadOnly)
            {
                return true;
            }

            var tip = $"文件 {file.Name} 是只读的，是否自动解除其只读属性？";
            if (DialogResult.Yes != MessageBox.Show(this, tip, "操作询问", MessageBoxButtons.YesNo))
            {
                lbMsg.Text = $"无法操作只读文件 {file.Name}";
                return false;
            }

            // 尝试移除只读属性
            try
            {
                File.SetAttributes(filename, FileAttributes.Normal);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, $"尝试解除文件 {file.Name} 的只读属性失败: {ex.Message}");
                lbMsg.Text = $"无法操作只读文件 {file.Name}";
                return false;
            }
        }

        private void btEncrypt_Click(object sender, EventArgs e)
        {
            if (encryptWorker.IsBusy)
            {
                return;
            }
            if (!checkFilePermission(tbFile.Text))
            {
                return;
            }
            if (!checkFilePermission(tbPubKey.Text))
            {
                return;
            }
            lbMsg.Text = "正在加密文件 ...";
            progressBar.Value = 0;
            lbPercent.Text = "0%";
            panel1.Enabled = false;
            lkOutput.Text = tbFile.Text + "." + DateTime.Now.ToString("yyyyMMddHHmmss") + EXT;
            encryptWorker.RunWorkerAsync();
        }

        private void btDecrypt_Click(object sender, EventArgs e)
        {
            if (decryptWorker.IsBusy)
            {
                return;
            }
            if (!checkFilePermission(tbFile.Text))
            {
                return;
            }
            if (!checkFilePermission(tbPrivKey.Text))
            {
                return;
            }
            lbMsg.Text = "正在解密文件 ...";
            progressBar.Value = 0;
            lbPercent.Text = "0%";
            panel1.Enabled = false;
            decryptWorker.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                var keyInfo = AES.MakeKeys();

                var keyData = keyInfo.GetBytes();

                var key = RSA.ReadKey(tbPubKey.Text);

                // 加密AES KEY
                var encryptedKey = RSA.Encrypt(keyData, keyData.Length, key);

                // 加密文件
                using (var outputStream = new FileStream(lkOutput.Text, FileMode.Create))
                {
                    // 写文件头
                    // 加密类型 1byte
                    outputStream.WriteByte((byte)KeyTypes.RSA_AES);
                    // KEY 长度 2byte
                    var keyLen = encryptedKey.Length;
                    if (keyLen > 0xffff)
                    {
                        throw new Exception("Key too long");
                    }
                    // 写 key长度
                    var temp = BitConverter.GetBytes((short)keyLen);
                    outputStream.Write(temp, 0, 2);

                    outputStream.Write(encryptedKey, 0, keyLen);

                    var filename = Path.GetFileName(tbFile.Text);
                    var filenameData = Encoding.UTF8.GetBytes(filename);
                    var filenameLen = filenameData.Length;

                    // 文件名长度 4 字节
                    temp = BitConverter.GetBytes(filenameLen);
                    outputStream.Write(temp, 0, 4);

                    // 写文件名
                    outputStream.Write(filenameData, 0, filenameLen);

                    // 使用 AES 加密文件

                    using (var inputStream = new FileStream(tbFile.Text, FileMode.Open))
                    {
                        // 写数据长度
                        var length = inputStream.Length;
                        temp = BitConverter.GetBytes((int)length);
                        outputStream.Write(temp, 0, 4);

                        var prevProgress = 0.0;

                        //var md5 = Utils.GetMD5(inputStream);

                        //// 写入原始的 MD5 (16) 字节
                        //outputStream.Write(md5, 0, md5.Length);

                        // 执行加密
                        AES.Encrypt(inputStream, outputStream, keyInfo, (handled) =>
                        {
                            var progress = (int)Math.Round(100.0 * handled / length);

                            if (progress != prevProgress)
                            {
                                prevProgress = progress;
                                encryptWorker.ReportProgress(progress);
                            }
                        });
                        inputStream.Close();
                    }

                    outputStream.Flush();
                    outputStream.Close();
                }
            }
            catch (Exception ex)
            {
                lbMsg.Invoke(new MethodInvoker(() =>
                {
                    lbMsg.Text = $"操作失败: {ex.Message}";
                }));
            }
            GC.Collect();
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lbPercent.Text = $"{e.ProgressPercentage}%";
            progressBar.Value = e.ProgressPercentage;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            lbMsg.Text = "操作完成！";

            panel1.Enabled = true;
        }

        private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                var key = RSA.ReadKey(tbPrivKey.Text);

                using (var inputStream = new FileStream(tbFile.Text, FileMode.Open))
                {
                    var keyType = inputStream.ReadByte();
                    if (keyType != (byte)KeyTypes.RSA_AES)
                    {
                        throw new Exception("Unsupported file.");
                    }

                    var temp = new byte[2];
                    inputStream.Read(temp, 0, 2);
                    var keyLen = BitConverter.ToInt16(temp, 0);
                    var keyData = new byte[keyLen];
                    inputStream.Read(keyData, 0, keyLen);

                    // 解密 AES KEY
                    var aesKeyData = RSA.Decrypt(keyData, key);
                    var keyInfo = new KeyInfo();
                    keyInfo.SetBytes(aesKeyData);

                    // 加载文件名长度
                    temp = new byte[4];
                    inputStream.Read(temp, 0, 4);
                    var filenameLen = BitConverter.ToInt32(temp, 0);


                    // 加载文件名
                    temp = new byte[filenameLen];
                    inputStream.Read(temp, 0, temp.Length);
                    var filename = Encoding.UTF8.GetString(temp);

                    // 加载数据长度
                    temp = new byte[4];
                    inputStream.Read(temp, 0, 4);
                    var length = BitConverter.ToInt32(temp, 0);

                    //// 加载 MD5
                    //var expectMd5 = new byte[16];
                    //inputStream.Read(expectMd5, 0, 16);

                    // 解密文件
                    var outputFileName = Path.GetFileNameWithoutExtension(filename) + "_" + DateTime.Now.ToString("yyyyMMddHHmmss") + Path.GetExtension(filename);
                    var outputFullName = Path.Combine(Path.GetDirectoryName(tbFile.Text), outputFileName);
                    lkOutput.Invoke(new MethodInvoker(() =>
                    {
                        lkOutput.Text = outputFullName;
                    }));

                    using (var outputStream = new FileStream(outputFullName, FileMode.Create))
                    {
                        var prevProgress = 0.0;

                        AES.Decrypt(inputStream, outputStream, keyInfo, length, (handled) =>
                        {
                            var progress = (int)Math.Round(100.0 * handled / length);

                            if (progress != prevProgress)
                            {
                                prevProgress = progress;
                                decryptWorker.ReportProgress(progress);
                            }
                        });

                        //var actualMd5 = Utils.GetMD5(outputStream);

                        //if (!Utils.Equals(expectMd5, actualMd5))
                        //{
                        //    lbMsg.Invoke(new MethodInvoker(() =>
                        //    {
                        //        lbMsg.Text = $"文件的MD5 {Utils.Bytes2String(actualMd5)} 与原始 MD5 {Utils.Bytes2String(expectMd5)}";
                        //    }));
                        //}
                        outputStream.Flush();
                        outputStream.Close();
                    }

                    inputStream.Close();
                }
            }
            catch (Exception ex)
            {
                lbMsg.Invoke(new MethodInvoker(() =>
                {
                    lbMsg.Text = $"操作失败: {ex.Message}";
                }));
            }
            GC.Collect();
        }

        private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lbPercent.Text = $"{e.ProgressPercentage}%";
            progressBar.Value = e.ProgressPercentage;
        }

        private void backgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            lbMsg.Text = "操作完成！";
            panel1.Enabled = true;
        }

        private void OnDragDrop(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                return;
            }
            ((TextBox)sender).BackColor = Color.White;
            var files = (string[])e.Data.GetData(DataFormats.FileDrop);
            ((TextBox)sender).Text = files[0];
        }

        private void OnDragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                return;
            }
            e.Effect = DragDropEffects.Copy;
            ((TextBox)sender).BackColor = Color.AliceBlue;
        }

        private void OnDragLeave(object sender, EventArgs e)
        {
            ((TextBox)sender).BackColor = Color.White;
        }

        private void lkOutput_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            Process.Start("explorer", $"/select,{lkOutput.Text}");
        }

        private void btnMd5_Click(object sender, EventArgs e)
        {
            if (md5Form == null)
            {
                md5Form = new Md5Form();
            }
            md5Form.SetFile(tbFile.Text, lkOutput.Text);
            md5Form.ShowDialog(this);
        }
    }
}
